package strategy

import "container/list"

// LRUCache LRU cache, unsafe in concurrent
type LRUCache struct {
	// max memory
	maxBytes int64
	// current memory
	nBytes int64
	// double linked list
	ll    *list.List
	cache map[string]*list.Element
	// callback for remove
	OnEvicted func(key string, value Value)
}

// NewLRUCache is a constructor of LRUCache
func NewLRUCache(maxBytes int64, onEvicted func(string, Value)) *LRUCache {
	return &LRUCache{
		maxBytes:  maxBytes,
		ll:        list.New(),
		cache:     make(map[string]*list.Element),
		OnEvicted: onEvicted,
	}
}

// Get method of LRUCache, look-ups a key's value
func (c *LRUCache) Get(key string) (value Value, ok bool) {
	if ele, ok := c.cache[key]; ok {
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry)
		return kv.value, true
	}
	return
}

// RemoveOldest method of LRUCache, remove the oldest element
func (c *LRUCache) RemoveOldest() {
	ele := c.ll.Back()
	if ele != nil {
		c.ll.Remove(ele)
		kv := ele.Value.(*entry)
		delete(c.cache, kv.key)
		c.nBytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
	}
}

// Add method of LRUCache, add a key-value pair
func (c *LRUCache) Add(key string, value Value) {
	if ele, ok := c.cache[key]; ok {
		// if exists, move to front
		c.ll.MoveToFront(ele)
		kv := ele.Value.(*entry)
		c.nBytes += int64(value.Len() - kv.value.Len())
		kv.value = value
	} else {
		ele := c.ll.PushFront(&entry{key: key, value: value})
		c.cache[key] = ele
		c.nBytes += int64(len(key) + value.Len())
	}
	// if cache is full, do remove
	for c.maxBytes != 0 && c.nBytes > c.maxBytes {
		c.RemoveOldest()
	}
}

func (c *LRUCache) Len() int {
	return c.ll.Len()
}

var _ Strategy = (*LRUCache)(nil)
